﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Game.model
{
    public class AggroList
    {
        private abstract class DamageHate
	    {
		  
		    public int hate;
		    public int damage;
	    }
	    public class HateInfo:DamageHate
	    {
		   
		    public Creature attacker;
		    
		    HateInfo(Creature attacker, AggroInfo ai)
		    {
			    this.attacker = attacker;
			    hate = ai.hate;
			    damage = ai.damage;
		    }
	    }
	    public class AggroInfo:DamageHate
	    {
		    public int attackerId;
		   
		    AggroInfo(Creature attacker)
		    {
			    attackerId = attacker.getObjectId();
		    }
	    }
	    public class DamageComparator:java.util.Comparator
	    {
		    /**
		     * Field instance.
		     */
		    private static Comparator<DamageHate> instance = new DamageComparator();
		
		    /**
		     * Method getInstance.
		     * @return Comparator<DamageHate>
		     */
		    public static Comparator<DamageHate> getInstance()
		    {
			    return instance;
		    }
		
		    /**
		     * Constructor for DamageComparator.
		     */
		    DamageComparator()
		    {
		    }
		
		    /**
		     * Method compare.
		     * @param o1 DamageHate
		     * @param o2 DamageHate
		     * @return int
		     */
		    @Override
		    public int compare(DamageHate o1, DamageHate o2)
		    {
			    return o2.damage - o1.damage;
		    }
	    }
	
	    /**
	     * @author Mobius
	     */
	    public static class HateComparator implements Comparator<DamageHate>
	    {
		    /**
		     * Field instance.
		     */
		    private static Comparator<DamageHate> instance = new HateComparator();
		
		    /**
		     * Method getInstance.
		     * @return Comparator<DamageHate>
		     */
		    public static Comparator<DamageHate> getInstance()
		    {
			    return instance;
		    }
		
		    /**
		     * Constructor for HateComparator.
		     */
		    HateComparator()
		    {
		    }
		
		    /**
		     * Method compare.
		     * @param o1 DamageHate
		     * @param o2 DamageHate
		     * @return int
		     */
		    @Override
		    public int compare(DamageHate o1, DamageHate o2)
		    {
			    int diff = o2.hate - o1.hate;
			    return diff == 0 ? o2.damage - o1.damage : diff;
		    }
	    }
	
	    /**
	     * Field npc.
	     */
	    private final NpcInstance npc;
	    /**
	     * Field hateList.
	     */
	    private final TIntObjectHashMap<AggroInfo> hateList = new TIntObjectHashMap<>();
	    /**
	     * Field lock.
	     */
	    private final ReadWriteLock @lock = new ReentrantReadWriteLock();
	    /**
	     * Field readLock.
	     */
	    private final Lock readLock = lock.readLock();
	    /**
	     * Field writeLock.
	     */
	    private final Lock writeLock = lock.writeLock();
	
	    /**
	     * Constructor for AggroList.
	     * @param npc NpcInstance
	     */
	    public AggroList(NpcInstance npc)
	    {
		    this.npc = npc;
	    }
	
	    /**
	     * Method addDamageHate.
	     * @param attacker Creature
	     * @param damage int
	     * @param aggro int
	     */
	    public void addDamageHate(Creature attacker, int damage, int aggro)
	    {
		    damage = Math.max(damage, 0);
		    if ((damage == 0) && (aggro == 0))
		    {
			    return;
		    }
		    writeLock.lock();
		    try
		    {
			    AggroInfo ai;
			    if ((ai = hateList.get(attacker.getObjectId())) == null)
			    {
				    hateList.put(attacker.getObjectId(), ai = new AggroInfo(attacker));
			    }
			    ai.damage += damage;
			    ai.hate += aggro;
			    ai.damage = Math.max(ai.damage, 0);
			    ai.hate = Math.max(ai.hate, 0);
		    }
		    finally
		    {
			    writeLock.unlock();
		    }
	    }
	
	    /**
	     * Method get.
	     * @param attacker Creature
	     * @return AggroInfo
	     */
	    public AggroInfo get(Creature attacker)
	    {
		    readLock.lock();
		    try
		    {
			    return hateList.get(attacker.getObjectId());
		    }
		    finally
		    {
			    readLock.unlock();
		    }
	    }
	
	    /**
	     * Method remove.
	     * @param attacker Creature
	     * @param onlyHate boolean
	     */
	    public void remove(Creature attacker, boolean onlyHate)
	    {
		    writeLock.lock();
		    try
		    {
			    if (!onlyHate)
			    {
				    hateList.remove(attacker.getObjectId());
				    return;
			    }
			    AggroInfo ai = hateList.get(attacker.getObjectId());
			    if (ai != null)
			    {
				    ai.hate = 0;
			    }
		    }
		    finally
		    {
			    writeLock.unlock();
		    }
	    }
	
	    /**
	     * Method clear.
	     */
	    public void clear()
	    {
		    clear(false);
	    }
	
	    /**
	     * Method clear.
	     * @param onlyHate boolean
	     */
	    public void clear(boolean onlyHate)
	    {
		    writeLock.lock();
		    try
		    {
			    if (hateList.isEmpty())
			    {
				    return;
			    }
			    if (!onlyHate)
			    {
				    hateList.clear();
				    return;
			    }
			    AggroInfo ai;
			    for (TIntObjectIterator<AggroInfo> itr = hateList.iterator(); itr.hasNext();)
			    {
				    itr.advance();
				    ai = itr.value();
				    ai.hate = 0;
				    if (ai.damage == 0)
				    {
					    itr.remove();
				    }
			    }
		    }
		    finally
		    {
			    writeLock.unlock();
		    }
	    }
	
	    /**
	     * Method isEmpty.
	     * @return boolean
	     */
	    public boolean isEmpty()
	    {
		    readLock.lock();
		    try
		    {
			    return hateList.isEmpty();
		    }
		    finally
		    {
			    readLock.unlock();
		    }
	    }
	
	    /**
	     * Method getHateList.
	     * @return List<Creature>
	     */
	    public List<Creature> getHateList()
	    {
		    AggroInfo[] hated;
		    readLock.lock();
		    try
		    {
			    if (hateList.isEmpty())
			    {
				    return Collections.emptyList();
			    }
			    hated = hateList.values(new AggroInfo[hateList.size()]);
		    }
		    finally
		    {
			    readLock.unlock();
		    }
		    Arrays.sort(hated, HateComparator.getInstance());
		    if (hated[0].hate == 0)
		    {
			    return Collections.emptyList();
		    }
		    List<Creature> hateList = new LazyArrayList<>();
		    List<Creature> chars = World.getAroundCharacters(npc);
		    AggroInfo ai;
		    for (AggroInfo element : hated)
		    {
			    ai = element;
			    if (ai.hate == 0)
			    {
				    continue;
			    }
			    for (Creature cha : chars)
			    {
				    if (cha.getObjectId() == ai.attackerId)
				    {
					    hateList.add(cha);
					    break;
				    }
			    }
		    }
		    return hateList;
	    }
	
	    /**
	     * Method getMostHated.
	     * @return Creature
	     */
	    public Creature getMostHated()
	    {
		    AggroInfo[] hated;
		    readLock.lock();
		    try
		    {
			    if (hateList.isEmpty())
			    {
				    return null;
			    }
			    hated = hateList.values(new AggroInfo[hateList.size()]);
		    }
		    finally
		    {
			    readLock.unlock();
		    }
		    Arrays.sort(hated, HateComparator.getInstance());
		    if (hated[0].hate == 0)
		    {
			    return null;
		    }
		    List<Creature> chars = World.getAroundCharacters(npc);
		    AggroInfo ai;
		    loop:
		    for (int i = 0; i < hated.length; i++)
		    {
			    ai = hated[i];
			    if (ai.hate == 0)
			    {
				    continue;
			    }
			    for (Creature cha : chars)
			    {
				    if (cha.getObjectId() == ai.attackerId)
				    {
					    if (cha.isDead())
					    {
						    continue loop;
					    }
					    return cha;
				    }
			    }
		    }
		    return null;
	    }
	
	    /**
	     * Method getRandomHated.
	     * @return Creature
	     */
	    public Creature getRandomHated()
	    {
		    AggroInfo[] hated;
		    readLock.lock();
		    try
		    {
			    if (hateList.isEmpty())
			    {
				    return null;
			    }
			    hated = hateList.values(new AggroInfo[hateList.size()]);
		    }
		    finally
		    {
			    readLock.unlock();
		    }
		    Arrays.sort(hated, HateComparator.getInstance());
		    if (hated[0].hate == 0)
		    {
			    return null;
		    }
		    List<Creature> chars = World.getAroundCharacters(npc);
		    LazyArrayList<Creature> randomHated = LazyArrayList.newInstance();
		    AggroInfo ai;
		    Creature mostHated;
		    loop:
		    for (int i = 0; i < hated.length; i++)
		    {
			    ai = hated[i];
			    if (ai.hate == 0)
			    {
				    continue;
			    }
			    for (Creature cha : chars)
			    {
				    if (cha.getObjectId() == ai.attackerId)
				    {
					    if (cha.isDead())
					    {
						    continue loop;
					    }
					    randomHated.add(cha);
					    break;
				    }
			    }
		    }
		    if (randomHated.isEmpty())
		    {
			    mostHated = null;
		    }
		    else
		    {
			    mostHated = randomHated.get(Rnd.get(randomHated.size()));
		    }
		    LazyArrayList.recycle(randomHated);
		    return mostHated;
	    }
	
	    /**
	     * Method getTopDamager.
	     * @return Creature
	     */
	    public Creature getTopDamager()
	    {
		    AggroInfo[] hated;
		    readLock.lock();
		    try
		    {
			    if (hateList.isEmpty())
			    {
				    return null;
			    }
			    hated = hateList.values(new AggroInfo[hateList.size()]);
		    }
		    finally
		    {
			    readLock.unlock();
		    }
		    Creature topDamager = null;
		    Arrays.sort(hated, DamageComparator.getInstance());
		    if (hated[0].damage == 0)
		    {
			    return null;
		    }
		    List<Creature> chars = World.getAroundCharacters(npc);
		    AggroInfo ai;
		    for (AggroInfo element : hated)
		    {
			    ai = element;
			    if (ai.damage == 0)
			    {
				    continue;
			    }
			    for (Creature cha : chars)
			    {
				    if (cha.getObjectId() == ai.attackerId)
				    {
					    topDamager = cha;
					    return topDamager;
				    }
			    }
		    }
		    return null;
	    }
	
	    /**
	     * Method getCharMap.
	     * @return Map<Creature,HateInfo>
	     */
	    public Map<Creature, HateInfo> getCharMap()
	    {
		    if (isEmpty())
		    {
			    return Collections.emptyMap();
		    }
		    Map<Creature, HateInfo> aggroMap = new HashMap<>();
		    List<Creature> chars = World.getAroundCharacters(npc);
		    readLock.lock();
		    try
		    {
			    AggroInfo ai;
			    for (TIntObjectIterator<AggroInfo> itr = hateList.iterator(); itr.hasNext();)
			    {
				    itr.advance();
				    ai = itr.value();
				    if ((ai.damage == 0) && (ai.hate == 0))
				    {
					    continue;
				    }
				    for (Creature attacker : chars)
				    {
					    if (attacker.getObjectId() == ai.attackerId)
					    {
						    aggroMap.put(attacker, new HateInfo(attacker, ai));
						    break;
					    }
				    }
			    }
		    }
		    finally
		    {
			    readLock.unlock();
		    }
		    return aggroMap;
	    }
	
	    /**
	     * Method getPlayableMap.
	     * @return Map<Playable,HateInfo>
	     */
	    public Map<Playable, HateInfo> getPlayableMap()
	    {
		    if (isEmpty())
		    {
			    return Collections.emptyMap();
		    }
		    Map<Playable, HateInfo> aggroMap = new HashMap<>();
		    List<Playable> chars = World.getAroundPlayables(npc);
		    readLock.lock();
		    try
		    {
			    AggroInfo ai;
			    for (TIntObjectIterator<AggroInfo> itr = hateList.iterator(); itr.hasNext();)
			    {
				    itr.advance();
				    ai = itr.value();
				    if ((ai.damage == 0) && (ai.hate == 0))
				    {
					    continue;
				    }
				    for (Playable attacker : chars)
				    {
					    if (attacker.getObjectId() == ai.attackerId)
					    {
						    aggroMap.put(attacker, new HateInfo(attacker, ai));
						    break;
					    }
				    }
			    }
		    }
		    finally
		    {
			    readLock.unlock();
		    }
		    return aggroMap;
	    }
    }
}
